package com.example.mybatisplus.pageplus;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

@Intercepts({
        @Signature(type = StatementHandler.class,//插件拦截的对象
                method = "prepare", //插件拦截的对象的方法
                args = {Connection.class, Integer.class}) //拦截的方法的参数
})
public class PagingPlugin implements Interceptor {
    private Integer defaultPage;//默认分页
    private Integer defaultPageSize;//默认每页条数
    private Boolean defaultUseFlag;//默认是否启动插件
    private Boolean defaultCheckFlag;//默认是否检测当前页码的有效性

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        //此处我们拦截的StatementHandler对象，所以方法参数传入的invocation是可以直接强转为StatementHandler
//        StatementHandler statementHandler = getUnProxyObject(invocation);
        MetaObject metaStatementHandler = getUnProxyObject(invocation);
//        //分离代理对象，获取最原始的被代理类
//
//
//        //取出需要执行的sql
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        if (!checkSelect(sql)) {
            return invocation.proceed();
        }
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        Object parameterObject = boundSql.getParameterObject();
        PageParams pageParams = getPageParams(parameterObject);
        if (pageParams == null) {//没有分页参数，不启用插件
            return invocation.proceed();
        }

        //获取分页参数，如果获取不到，则使用默认值
        Integer pageNum = pageParams.getPage() == null ? this.defaultPage : pageParams.getPage();
        Integer pageSize = pageParams.getPageSize() == null ? this.defaultPageSize : pageParams.getPageSize();
        Boolean useFlag = pageParams.getUseFlag() == null ? this.defaultUseFlag : pageParams.getUseFlag();
        Boolean checkFlag = pageParams.getCheckFlag() == null ? this.defaultCheckFlag : pageParams.getCheckFlag();
        if (!useFlag) {//不使用分页插件
            return invocation.proceed();
        }
        //获取总记录数
        int total = getTotal(invocation, metaStatementHandler, boundSql);
        //回填总数到分页参数中
        this.SetTotalToPageParams(pageParams, total, pageSize);
        //检查当前页码有效性
        this.checkPage(checkFlag, pageNum, pageParams.getTotalPage());

        return changeSQL(invocation, metaStatementHandler, boundSql, pageNum, pageSize);

    }

    private void checkPage(Boolean checkFlag, Integer pageNum, Integer totalPage) throws Exception {
        if (checkFlag) {
            if (pageNum > totalPage) {
                throw new Exception("查询失败，查询页码" + pageNum + ">查询总页数" + totalPage);
            }
        }
    }

    private Object changeSQL(Invocation invocation, MetaObject metaStatementHandler, BoundSql boundSql, Integer pageNum, Integer pageSize) throws InvocationTargetException, IllegalAccessException, SQLException {
        String dbType = "mysql";
        //获取当前需要执行的SQL
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        //修改SQL，变成分页SQL
        String newSql = "";
        if ("mysql".equalsIgnoreCase(dbType)) {
            newSql = "select * from ( " + sql + " ) temp limit ?,?";
        } else if ("oracle".equalsIgnoreCase(dbType)) {
            newSql = "select * from ( select temp.*,rownum rw from ( " + sql + " ) temp where rownum<=? ) where rw>?";
        }
        //修改当前需要执行的SQL
        metaStatementHandler.setValue("delegate.boundSql.sql", newSql);
        //获取PrepareStatement，为其设置分页参数
        PreparedStatement ps = (PreparedStatement) invocation.proceed();
        int parameterCount = ps.getParameterMetaData().getParameterCount();
        if ("mysql".equalsIgnoreCase(dbType)) {
            ps.setInt(parameterCount - 1, (pageNum - 1) * pageSize);
            ps.setInt(parameterCount, pageSize);
        } else if ("oracle".equalsIgnoreCase(dbType)) {
            ps.setInt(parameterCount - 1, pageNum * pageSize);
            ps.setInt(parameterCount, (pageNum - 1) * pageSize + 1);
        }
        return ps;
    }

    private void SetTotalToPageParams(PageParams pageParams, int total, Integer pageSize) {
        pageParams.setTotal(total);
        int totalPage = total % pageSize == 0 ? total / pageSize : total / pageSize + 1;
        pageParams.setTotalPage(totalPage);
    }

    private int getTotal(Invocation invocation, MetaObject metaStatementHandler, BoundSql boundSql) {
        //获取当前的mappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        //获取配置对象
        Configuration cfg = mappedStatement.getConfiguration();
        //获取当前需要执行的SQL
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        String countSql = "select count(*) as \"total\" from ( " + sql + " ) temp";
        //获取拦截方法参数，我们知道是Connection对象
        Connection connection = (Connection) invocation.getArgs()[0];
        PreparedStatement ps = null;
        int total = 0;
        try {
            //预编译统计总数SQL
            ps = connection.prepareStatement(countSql);
            //构建统计总数BoundSql
            BoundSql countBoundSql = new BoundSql(cfg, countSql, boundSql.getParameterMappings(), boundSql.getParameterObject());
            //构建Mybatis的ParameterHandler用来设置查询总数SQL的参数
            ParameterHandler handler = new DefaultParameterHandler(mappedStatement, boundSql.getParameterObject(), countBoundSql);
            //设置查询总数参数
            handler.setParameters(ps);
            //执行查询
            ResultSet rs = ps.executeQuery();
            while (rs.next()) {
                total = rs.getInt("total");
            }
        } catch (Exception e) {

        } finally {
            if (ps != null) {
                try {
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        System.err.println("总条数：" + total);
        return total;
    }

    private PageParams getPageParams(Object parameterObject) {
        if (parameterObject == null) {
            return null;
        }
        PageParams pageParams = null;
        if (parameterObject instanceof Map) {
            @SuppressWarnings("unchecked")
            Map<String, Object> paramMap = (Map<String, Object>) parameterObject;
            Set<String> keySet = paramMap.keySet();
            Iterator<String> iterator = keySet.iterator();
            while (iterator.hasNext()) {
                String key = iterator.next();
                Object value = paramMap.get(key);
                if (value instanceof PageParams) {
                    return (PageParams) value;
                }
            }
        } else if (parameterObject instanceof PageParams) {//继承方式
            pageParams = (PageParams) parameterObject;
        }
        return pageParams;
    }

    private boolean checkSelect(String sql) {
        String trimSql = sql.trim();
        int idx = trimSql.toLowerCase().indexOf("select");
        return idx == 0;
    }

    private MetaObject getUnProxyObject(Invocation invocation) {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        while (metaStatementHandler.hasGetter("h")) {
            Object object = metaStatementHandler.getValue("h");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }

        while (metaStatementHandler.hasGetter("target")) {
            Object object = metaStatementHandler.getValue("target");
            metaStatementHandler = SystemMetaObject.forObject(object);
        }
        return metaStatementHandler;
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties props) {
        String defaultPage = props.getProperty("default.page", "1");
        String defaultPageSize = props.getProperty("default.pageSize", "20");
        String defaultUseFlag = props.getProperty("default.useFlag", "false");
        String defaultCheckFlag = props.getProperty("default.checkFlag", "false");
        this.defaultPage = Integer.parseInt(defaultPage);
        this.defaultPageSize = Integer.parseInt(defaultPageSize);
        this.defaultUseFlag = Boolean.parseBoolean(defaultUseFlag);
        this.defaultCheckFlag = Boolean.parseBoolean(defaultCheckFlag);
    }
}